/* This is sample log system
 * histort:
 *      2020.07.15 base function
 */

#ifndef _LOG_H_
#define _LOG_H_

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <stdlib.h>

#define NONE      "\e[0m"
#define BLACK     "\e[0;30m"
#define L_BLACK   "\e[1;30m"
#define RED       "\e[0;31m"
#define L_RED     "\e[1;31m"
#define GREEN     "\e[0;32m"
#define L_GREEN   "\e[1;32m"
#define BROWN     "\e[0;33m"
#define YELLOW    "\e[1;33m"
#define BLUE      "\e[0;34m"
#define L_BLUE    "\e[1;34m"
#define PURPLE    "\e[0;35m"
#define L_PURPLE  "\e[1;35m"
#define CYAN      "\e[0;36m"
#define L_CYAN    "\e[1;36m"
#define GRAY      "\e[0;37m"
#define WHITE     "\e[1;37m"

#define BOLD      "\e[1m"
#define UNDERLINE "\e[4m"
#define BLINK     "\e[5m"
#define REVERSE   "\e[7m"
#define HIDE      "\e[8m"
#define CLEAR     "\e[2J"
#define CLRLINE   "\r\e[K" //or "\e[1K\r"

enum LOG_LEVEL {
  LOG_LEVEL_ERROR = 0,
  LOG_LEVEL_WARN,
  LOG_LEVEL_INFO,
  LOG_LEVEL_DEBUG,
  LOG_LEVEL_VERBOSE,
  LOG_LEVEL_MAX
};

static char g_file_name[256];
static FILE *g_fp = NULL;
static enum LOG_LEVEL g_log_level = LOG_LEVEL_INFO;
__attribute__((unused)) static time_t now;
__attribute__((unused)) static struct tm *tm_now;
extern FILE *g_fp;

#define __FILENAME__ (strrchr(__FILE__, '/') + 1)

#define UNUSED(expr) \
  do {               \
    (void)(expr);    \
  } while (0)


#define LOG_PRINT_T(fmt, args...) \
  if (g_fp != NULL) { \
    fprintf(g_fp, fmt, ##args); \
  } else if (g_log_level >= LOG_LEVEL_ERROR) { \
    printf(fmt, ##args);\
  }

#define LOG_PERROR_T(fmt, args...) \
  if (g_fp != NULL) { \
    time(&now); tm_now = localtime(&now); \
    fprintf(g_fp, "[%d-%02d-%02d %02d:%02d:%02d] ", tm_now->tm_year+1900, tm_now->tm_mon+1,  \
        tm_now->tm_mday, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec);  \
    fprintf(g_fp, "[E][%d][%s] " fmt ": %s\n", __LINE__, __FILENAME__, ##args, strerror(errno));\
  } else if (g_log_level >= LOG_LEVEL_ERROR) { \
    time(&now); tm_now = localtime(&now); \
    printf("[%d-%02d-%02d %02d:%02d:%02d] ", tm_now->tm_year+1900, tm_now->tm_mon+1,  \
        tm_now->tm_mday, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec); \
    printf("[E][%d][%s] " fmt ": %s\n", __LINE__, __FILENAME__, ##args, strerror(errno));\
  }

#define LOG_ERROR_T(fmt, args...) \
  if (g_fp != NULL) { \
    time(&now); tm_now = localtime(&now); \
    fprintf(g_fp, "[%d-%02d-%02d %02d:%02d:%02d] ", tm_now->tm_year+1900, tm_now->tm_mon+1,  \
        tm_now->tm_mday, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec);  \
    fprintf(g_fp, "[E][%d][%s] " fmt, __LINE__, __FILENAME__, ##args);\
  } else if (g_log_level >= LOG_LEVEL_ERROR) { \
    time(&now); tm_now = localtime(&now); \
    printf("[%d-%02d-%02d %02d:%02d:%02d] ", tm_now->tm_year+1900, tm_now->tm_mon+1,  \
        tm_now->tm_mday, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec); \
    printf("[E][%d][%s] " fmt, __LINE__, __FILENAME__, ##args);\
  }

#define LOG_WARN_T(fmt, args...) \
  if (g_fp != NULL) { \
    time(&now); tm_now = localtime(&now); \
    fprintf(g_fp, "[%d-%02d-%02d %02d:%02d:%02d] ", tm_now->tm_year+1900, tm_now->tm_mon+1,  \
        tm_now->tm_mday, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec);  \
    fprintf(g_fp, "[W][%d][%s] " fmt, __LINE__, __FILENAME__, ##args);\
  } else if (g_log_level >= LOG_LEVEL_WARN) { \
    time(&now); tm_now = localtime(&now); \
    printf("[%d-%02d-%02d %02d:%02d:%02d] ", tm_now->tm_year+1900, tm_now->tm_mon+1,  \
        tm_now->tm_mday, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec); \
    printf("[W][%d][%s] " fmt, __LINE__, __FILENAME__, ##args);\
  }

#define LOG_INFO_T(fmt, args...) \
  if (g_fp != NULL) { \
    time(&now); tm_now = localtime(&now); \
    fprintf(g_fp, "[%d-%02d-%02d %02d:%02d:%02d] ", tm_now->tm_year+1900, tm_now->tm_mon+1,  \
        tm_now->tm_mday, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec);  \
    fprintf(g_fp, "[I][%d][%s] " fmt, __LINE__, __FILENAME__, ##args);\
  } else if (g_log_level >= LOG_LEVEL_INFO) { \
    time(&now); tm_now = localtime(&now); \
    printf("[%d-%02d-%02d %02d:%02d:%02d] ", tm_now->tm_year+1900, tm_now->tm_mon+1,  \
        tm_now->tm_mday, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec); \
    printf("[I][%d][%s] " fmt, __LINE__, __FILENAME__, ##args);\
  }

#define LOG_DEBUG_T(fmt, args...) \
  if (g_fp != NULL) { \
    time(&now); tm_now = localtime(&now); \
    fprintf(g_fp, "[%d-%02d-%02d %02d:%02d:%02d] ", tm_now->tm_year+1900, tm_now->tm_mon+1,  \
        tm_now->tm_mday, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec);  \
    fprintf(g_fp, "[D][%d][%s] " fmt, __LINE__, __FILENAME__, ##args);\
  } else if (g_log_level >= LOG_LEVEL_DEBUG) { \
    time(&now); tm_now = localtime(&now); \
    printf("[%d-%02d-%02d %02d:%02d:%02d] ", tm_now->tm_year+1900, tm_now->tm_mon+1,  \
        tm_now->tm_mday, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec); \
    printf("[D][%d][%s] " fmt, __LINE__, __FILENAME__, ##args);\
  }

#define LOG_VERBOSE_T(fmt, args...) \
  if (g_fp != NULL) { \
    time(&now); tm_now = localtime(&now); \
    fprintf(g_fp, "[%d-%02d-%02d %02d:%02d:%02d] ", tm_now->tm_year+1900, tm_now->tm_mon+1,  \
        tm_now->tm_mday, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec);  \
    fprintf(g_fp, "[V][%d][%s] " fmt, __LINE__, __FILENAME__, ##args);\
  } else if (g_log_level >= LOG_LEVEL_VERBOSE) { \
    time(&now); tm_now = localtime(&now); \
    printf("[%d-%02d-%02d %02d:%02d:%02d] ", tm_now->tm_year+1900, tm_now->tm_mon+1,  \
        tm_now->tm_mday, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec); \
    printf("[V][%d][%s] " fmt, __LINE__, __FILENAME__, ##args);\
  }

#define LOG_PRINT(fmt, args...) \
    LOG_PRINT_T(fmt, ##args);
#define LOG_PERROR(fmt, args...) \
    printf(RED); LOG_PERROR_T(fmt, ##args); printf(NONE);
#define LOG_ERROR(fmt, args...) \
    printf(RED); LOG_ERROR_T(fmt, ##args); printf(NONE);
#define LOG_WARN(fmt, args...) \
    printf(BROWN); LOG_WARN_T(fmt, ##args); printf(NONE);
#define LOG_INFO(fmt, args...) \
    printf(GREEN); LOG_INFO_T(fmt, ##args); printf(NONE);
#define LOG_DEBUG(fmt, args...) \
    printf(BLUE); LOG_DEBUG_T(fmt, ##args); printf(NONE);
#define LOG_VERBOSE(fmt, args...) \
    LOG_VERBOSE_T(fmt, ##args);

#if 0
#if !defined(RETURN_IF_NULL)
#define RETURN_IF_NULL(ptr)          \
  if (ptr == nullptr) {              \
    LOG_ERROR(#ptr" is nullptr.\n");   \
    return;                          \
  }
#endif

#if !defined(RETURN_VAL_IF_NULL)
#define RETURN_VAL_IF_NULL(ptr, val) \
  if (ptr == nullptr) {              \
    LOG_ERROR(#ptr" is nullptr.\n");   \
    return val;                      \
  }
#endif

#if !defined(RETURN_IF)
#define RETURN_IF(condition)           \
  if (condition) {                     \
    LOG_ERROR(#condition" is met.\n"); \
    return;                            \
  }
#endif

#if !defined(RETURN_VAL_IF)
#define RETURN_VAL_IF(condition, val)  \
  if (condition) {                     \
    LOG_ERROR(#condition" is met.\n"); \
    return val;                        \
  }
#endif

#endif // endif if0


inline void log_init() {
  //print = PERROE = ERROE < WARN < INFO < DEBUG
  const char *env = getenv("TLOG_LEVEL");
  // printf("env = %s\n", env);
  if (env != NULL && strlen(env) != 0) {
    if (strcmp(env, "error") == 0)
      g_log_level = LOG_LEVEL_ERROR;
    else if (strcmp(env, "warn") == 0)
      g_log_level = LOG_LEVEL_WARN;
    else if (strcmp(env, "info") == 0)
      g_log_level = LOG_LEVEL_INFO;
    else if (strcmp(env, "debug") == 0)
      g_log_level = LOG_LEVEL_DEBUG;
    else if (strcmp(env, "verbose") == 0)
      g_log_level = LOG_LEVEL_VERBOSE;
    else g_log_level = LOG_LEVEL_INFO;
  }
  //strcpy(g_file_name, "./log.txt");
  env = getenv("TLOG_FILE");
  // printf("env = %s\n", env);
  if (env != NULL && strlen(env) != 0) {
    if (strcmp(env, "nofile") != 0) {
      strcpy(g_file_name, env);
      g_fp = fopen(g_file_name, "a");
      if (g_fp == NULL) {
        perror("fopen failed");
        strcpy(g_file_name, "nofile");
      }
    }
  }
}

inline void log_deinit() {
  if (g_fp != NULL) {
    fflush(g_fp);
    fclose(g_fp);
  }
}

inline enum LOG_LEVEL get_log_level() {
  return g_log_level;
}

inline void set_log_level(enum LOG_LEVEL log_level) {
  g_log_level = log_level;
}

#endif  // endif _LOG_H_
